﻿Imports System.ComponentModel
Imports System.Text.RegularExpressions
Imports System.IO

Public Class FindInFilesResultsWindow
    Implements INotifyPropertyChanged

#Region "Events"
    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
#End Region

#Region "Properties"
    Private ReadOnly _copyFileListToClipboard As New CopyFileListToClipboardCommand(Me)
    Public ReadOnly Property CopyFileListToClipboard As ICommand
        Get
            Return _copyFileListToClipboard
        End Get
    End Property

    Private ReadOnly _copyLineNumbersToClipboard As New CopyLineNumbersToClipboardCommand(Me)
    Public ReadOnly Property CopyLineNumbersToClipboard As ICommand
        Get
            Return _copyLineNumbersToClipboard
        End Get
    End Property

    Private _regexPattern As String
    Public Property RegexPattern() As String
        Get
            Return _regexPattern
        End Get
        Set(ByVal value As String)
            _regexPattern = value
            OnPropertyChanged(New PropertyChangedEventArgs("RegexPattern"))
        End Set
    End Property

    Private _regexOptions As RegexOptions
    Public Property RegexOptions() As RegexOptions
        Get
            Return _regexOptions
        End Get
        Set(ByVal value As RegexOptions)
            _regexOptions = value
            OnPropertyChanged(New PropertyChangedEventArgs("RegexOptions"))
        End Set
    End Property

    Private _findInFilesFolder As String
    Public Property FindInFilesFolder() As String
        Get
            Return _findInFilesFolder
        End Get
        Set(ByVal value As String)
            _findInFilesFolder = value
            OnPropertyChanged(New PropertyChangedEventArgs("FindInFilesFolder"))
        End Set
    End Property

    Private _searchInSubFolders As Boolean
    Public Property SearchInSubFolders() As Boolean
        Get
            Return _searchInSubFolders
        End Get
        Set(ByVal value As Boolean)
            _searchInSubFolders = value
            OnPropertyChanged(New PropertyChangedEventArgs("SearchInSubFolders"))
        End Set
    End Property

    Private _fileNameFilter As String = ""
    Public Property FileNameFilter() As String
        Get
            Return _fileNameFilter
        End Get
        Set(ByVal value As String)
            _fileNameFilter = value
            OnPropertyChanged(New PropertyChangedEventArgs("FileNameFilter"))
        End Set
    End Property

    Private _matchResults As IEnumerable(Of DirectoryGroup)
    Public Property MatchResults() As IEnumerable(Of DirectoryGroup)
        Get
            Return _matchResults
        End Get
        Set(ByVal value As IEnumerable(Of DirectoryGroup))
            _matchResults = value
            OnPropertyChanged(New PropertyChangedEventArgs("MatchResults"))
        End Set
    End Property

    Private _totalOccurrences As Integer
    Public Property TotalOccurrences() As Integer
        Get
            Return _totalOccurrences
        End Get
        Private Set(ByVal value As Integer)
            _totalOccurrences = value
        End Set
    End Property

    Private _totalFiles As Integer
    Public Property TotalFiles() As Integer
        Get
            Return _totalFiles
        End Get
        Private Set(ByVal value As Integer)
            _totalFiles = value
        End Set
    End Property

#End Region

#Region "Constructors"

#End Region

#Region "Methods"
    Private Sub LoadMatches()

        If Directory.Exists(Me.FindInFilesFolder) Then
            Dim l_files =
                From f In Directory.GetFiles(Me.FindInFilesFolder, "*.*", If(Me.SearchInSubFolders, SearchOption.AllDirectories, SearchOption.TopDirectoryOnly))
                Where Regex.IsMatch(f, If(Me.FileNameFilter, ""))
                Order By f
                Select New FileInfo(f)

            Dim l_groupedFiles =
                From f In l_files
                Group f By DirectoryName = f.DirectoryName Into Files = Group

            Dim l_regex As New Regex(Me.RegexPattern, Me.RegexOptions)

            Dim l_matchResults As New List(Of DirectoryGroup)

            For Each l_group In l_groupedFiles
                Dim l_fileMatches As New List(Of FileMatch)

                For Each l_file In l_group.Files
                    Dim l_stream = File.Open(l_file.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
                    Dim l_reader As New StreamReader(l_stream)
                    Dim l_fileContents = l_reader.ReadToEnd()
                    l_stream.Close()

                    Dim l_getLineNumber =
                        Function(index As Integer) As Integer
                            Dim l_lineTerminators = Regex.Matches(l_fileContents.Substring(0, index + 1), "\n")
                            Return If(l_lineTerminators IsNot Nothing, l_lineTerminators.Count + 1, 0)
                        End Function

                    Dim l_getColumnNumber =
                        Function(index As Integer) As Integer
                            Dim l_precedingCharacters = Regex.Match(l_fileContents.Substring(0, index), "^.*", Text.RegularExpressions.RegexOptions.Multiline Or Text.RegularExpressions.RegexOptions.RightToLeft)
                            If l_precedingCharacters IsNot Nothing AndAlso l_precedingCharacters.Success Then
                                Return l_precedingCharacters.Value.Length + 1 'The first column (0) should be counted as 1, etc.
                            Else
                                Return 0
                            End If
                        End Function

                    Dim l_matches = l_regex.Matches(l_fileContents).OfType(Of Match)()
                    If l_matches.Any() Then
                        l_fileMatches.Add(
                            New FileMatch(
                                FileInfo:=l_file,
                                snippets:=
                                    (
                                        From m In l_matches
                                        Select New FileMatchSnippet(
                                            Match:=m,
                                            Line:=l_getLineNumber(m.Index),
                                            column:=l_getColumnNumber(m.Index)
                                        )
                                    ).ToList()
                            )
                        )
                    End If

                    'Help the garbage collector along a bit...
                    l_fileContents = Nothing
                Next

                If l_fileMatches.Count > 0 Then
                    l_matchResults.Add(
                        New DirectoryGroup(
                            New DirectoryInfo(l_group.DirectoryName),
                            l_fileMatches,
                            New DirectoryInfo(Me.FindInFilesFolder)
                        )
                    )
                End If
            Next


            Me.MatchResults = l_matchResults

            Me.TotalFiles = (From d In Me.MatchResults From f In d.FileMatches).Count()
            Me.TotalOccurrences = (From d In Me.MatchResults From f In d.FileMatches From s In f.Snippets).Count()
        End If

    End Sub
#End Region

#Region "Event Raiser Methods"
    Private Overloads Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
        RaiseEvent PropertyChanged(sender:=Me, e:=e)
    End Sub
#End Region

#Region "Event Handlers"
    Private Sub FindInFilesResultsWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        LoadMatches()
    End Sub
#End Region

End Class

Public Class DirectoryGroup

    Private _fileMatches As IEnumerable(Of FileMatch)
    Public Property FileMatches() As IEnumerable(Of FileMatch)
        Get
            Return _fileMatches
        End Get
        Private Set(ByVal value As IEnumerable(Of FileMatch))
            _fileMatches = value
        End Set
    End Property

    Private _directoryInfo As DirectoryInfo
    Public Property DirectoryInfo() As DirectoryInfo
        Get
            Return _directoryInfo
        End Get
        Private Set(ByVal value As DirectoryInfo)
            _directoryInfo = value
        End Set
    End Property

    Private _searchDirectory As DirectoryInfo
    Public Property SearchDirectory() As DirectoryInfo
        Get
            Return _searchDirectory
        End Get
        Private Set(ByVal value As DirectoryInfo)
            _searchDirectory = value
        End Set
    End Property

    Public ReadOnly Property RelativePath() As String
        Get
            If Me.SearchDirectory IsNot Nothing Then
                Return DirectoryInfo.FullName.Replace(SearchDirectory.FullName, "")
            Else
                Return DirectoryInfo.FullName
            End If
            Return ""
        End Get
    End Property

    Public Sub New(ByVal directoryInfo As DirectoryInfo, ByVal fileMatches As IEnumerable(Of FileMatch), Optional ByVal searchDirectory As DirectoryInfo = Nothing)
        Me.DirectoryInfo = directoryInfo
        Me.FileMatches = fileMatches
        Me.SearchDirectory = searchDirectory
    End Sub

End Class

Public Class FileMatch

    Private _fileInfo As FileInfo
    Public Property FileInfo() As FileInfo
        Get
            Return _fileInfo
        End Get
        Private Set(ByVal value As FileInfo)
            _fileInfo = value
        End Set
    End Property

    Private _snippets As IEnumerable(Of FileMatchSnippet)
    Public Property Snippets() As IEnumerable(Of FileMatchSnippet)
        Get
            Return _snippets
        End Get
        Private Set(ByVal value As IEnumerable(Of FileMatchSnippet))
            _snippets = value
        End Set
    End Property

    Public Sub New(ByVal fileInfo As FileInfo, ByVal snippets As IEnumerable(Of FileMatchSnippet))
        Me.FileInfo = fileInfo
        Me.Snippets = snippets
    End Sub

End Class

Public Class FileMatchSnippet

    Private _matchResult As MatchResult
    Public Property MatchResult() As MatchResult
        Get
            Return _matchResult
        End Get
        Private Set(ByVal value As MatchResult)
            _matchResult = value
        End Set
    End Property

    Private _line As Integer
    Public Property Line() As Integer
        Get
            Return _line
        End Get
        Private Set(ByVal value As Integer)
            _line = value
        End Set
    End Property

    Private _column As String
    Public Property Column() As String
        Get
            Return _column
        End Get
        Private Set(ByVal value As String)
            _column = value
        End Set
    End Property

    Private _snippet As String
    Public Property Snippet() As String
        Get
            Return _snippet
        End Get
        Private Set(ByVal value As String)
            _snippet = value
        End Set
    End Property

    Public Sub New(ByVal match As Match, ByVal line As Integer, ByVal column As Integer)
        Me.MatchResult = New MatchResult(match)
        Me.Line = line
        Me.Column = column
        Me.Snippet = String.Join("", match.Value.Take(30).ToArray())
    End Sub

End Class

Public Class MatchResult

    Private _match As Match
    Public Property Match() As Match
        Get
            Return _match
        End Get
        Private Set(ByVal value As Match)
            _match = value
        End Set
    End Property

    Private _details As IEnumerable(Of MatchResultDetail)
    Public Property Details As IEnumerable(Of MatchResultDetail)
        Get
            Return _details
        End Get
        Private Set(ByVal value As IEnumerable(Of MatchResultDetail))
            _details = value
        End Set
    End Property

    Friend Sub New(ByVal match As Match)
        Me.Match = match
        Me.Details = New MatchResultDetail() {
            New MatchResultDetail("Captures", Me.Match.Captures),
            New MatchResultDetail("Groups", Me.Match.Groups)
        }
    End Sub

End Class

Public Class MatchResultDetail

    Private _title As String
    Public Property Title() As String
        Get
            Return _title
        End Get
        Private Set(ByVal value As String)
            _title = value
        End Set
    End Property

    Private _items As IEnumerable
    Public Property Items() As IEnumerable
        Get
            Return _items
        End Get
        Private Set(ByVal value As IEnumerable)
            _items = value
        End Set
    End Property

    Friend Sub New(ByVal title As String, ByVal items As IEnumerable)
        Me.Title = title
        Me.Items = items
    End Sub

End Class



#Region "Command Classes"
Partial Class FindInFilesResultsWindow


#Region "CopyFileListToClipboardCommand"
    Private Class CopyFileListToClipboardCommand
        Implements ICommand

        Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged

        Private ReadOnly _parent As FindInFilesResultsWindow

        Public Sub New(ByVal parent As FindInFilesResultsWindow)
            _parent = parent
        End Sub

        Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
            Return True
        End Function

        Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
            Clipboard.SetText(
                String.Join(
                    Environment.NewLine,
                    (
                        From d In _parent.MatchResults
                        From f In d.FileMatches
                        Select f.FileInfo.FullName
                    ).ToArray()
                )
            )
        End Sub
    End Class

    Private Class CopyLineNumbersToClipboardCommand
        Implements ICommand

        Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged

        Private ReadOnly _parent As FindInFilesResultsWindow

        Public Sub New(ByVal parent As FindInFilesResultsWindow)
            _parent = parent
        End Sub

        Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
            Return True
        End Function

        Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
            Dim l_columnHeader(2) As String
            l_columnHeader(0) = "Directory (Relative to Search)"
            l_columnHeader(1) = "File Name"
            l_columnHeader(2) = "Matches"
            
            Dim l_columnWidth(2) As Integer
            l_columnWidth(0) = Math.Max((From d In _parent.MatchResults Select d.RelativePath.Length).Max(), l_columnHeader(0).Length)
            l_columnWidth(1) = Math.Max((From d In _parent.MatchResults From f In d.FileMatches Select f.FileInfo.Name.Length).Max(), l_columnHeader(1).Length)
            l_columnWidth(2) = 31

            Dim l_columnPadding = 2
            Dim l_columnPaddingSpacer = "".PadRight(l_columnPadding)

            Dim l_resultsTableWidth = l_columnWidth.Sum() + (l_columnPadding * l_columnWidth.Length - 1)

            For l_columnIndex As Integer = 0 To 2
                l_columnHeader(l_columnIndex) = l_columnHeader(l_columnIndex).PadRight(l_columnWidth(l_columnIndex))
            Next

            Dim l_indentColumnSpacer(1) As String
            l_indentColumnSpacer(0) = "".PadRight(l_columnWidth(0))
            l_indentColumnSpacer(1) = l_indentColumnSpacer(0) & l_columnPaddingSpacer & "".PadRight(l_columnWidth(1))

            Dim l_parameters = New Object() {
                New String() {
                    "Search In",
                    _parent.FindInFilesFolder
                },
                New String() {
                    "Include Sub-Folders",
                    If(_parent.SearchInSubFolders, "YES", "NO")
                },
                New String() {
                    "File Name Filter",
                    _parent.FileNameFilter
                },
                New String() {
                    "Expression",
                    String.Join(vbLf & "".PadRight(21), _parent.RegexPattern.Split(vbLf))
                }
            }

            Dim l_heading =
                "PARAMETERS" & Environment.NewLine &
                Environment.NewLine &
                StrDup(19, "-") & "  " & StrDup(l_resultsTableWidth - 21, "-") & Environment.NewLine &
                String.Join(
                    Environment.NewLine,
                    l_parameters.Select(Function(p As String()) p(0).PadRight(19) & "  " & String.Join(vbLf & "".PadRight(21), p(1).Split(vbLf))).ToArray()
                ) & Environment.NewLine &
                Environment.NewLine &
                "Found " & _parent.TotalOccurrences & " occurrences in " & _parent.TotalFiles & " files" & Environment.NewLine &
                Environment.NewLine &
                "RESULTS" & Environment.NewLine &
                Environment.NewLine

            Dim l_resultsTable =
                l_columnHeader(0).PadRight(l_columnWidth(0)) & l_columnPaddingSpacer & l_columnHeader(1).PadRight(l_columnWidth(1)) & l_columnPaddingSpacer & l_columnHeader(2).PadRight(l_columnWidth(2)) & Environment.NewLine &
                String.Join(
                    Environment.NewLine &
                    Environment.NewLine,
                    (
                        From d In _parent.MatchResults
                        Select
                            (
                                StrDup(l_columnWidth(0), "-") & l_columnPaddingSpacer & StrDup(l_columnWidth(1), "-") & l_columnPaddingSpacer & StrDup(31, "-") & Environment.NewLine &
                                d.RelativePath.PadRight(l_columnWidth(0)) &
                                String.Join(
                                    Environment.NewLine &
                                    Environment.NewLine &
                                    l_columnPaddingSpacer & l_indentColumnSpacer(0) & StrDup(l_columnWidth(1), "-") & l_columnPaddingSpacer & StrDup(l_columnWidth(2), "-") & Environment.NewLine &
                                    l_indentColumnSpacer(0),
                                    (
                                        From f In d.FileMatches
                                        Select
                                            (
                                                "  " & f.FileInfo.Name.PadRight(l_columnWidth(1)) &
                                                String.Join(
                                                    Environment.NewLine &
                                                    l_indentColumnSpacer(1),
                                                    (
                                                        From s In f.Snippets
                                                        Select "  Match on Line " & s.Line & ", Column " & s.Column
                                                    ).ToArray())
                                            )
                                    ).ToArray()
                                )
                        )
                    ).ToArray()
                )

            Clipboard.SetText(
                l_heading & l_resultsTable
            )
        End Sub
    End Class
#End Region


End Class
#End Region
